package ddpserver;


import java.net.*;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.io.*;
import java.lang.reflect.InvocationTargetException;

import ddpdecoder.*;

public class Server
{
	private static final int PORT = 1234;
    private static CommandReader commandReader=new Server.CommandReader();;  
	private static ArrayList<FrequencyShifter> shifterList = new ArrayList<FrequencyShifter>();
	
    public static void main( String[] args ) throws IOException, 
	InvocationTargetException, InterruptedException
    {
        server();							               //Start the server service
    }
	
	public static void server() throws InvocationTargetException, InterruptedException
    {
		startMonitorInput();
        ExecutorService exec = Executors.newCachedThreadPool();  //use thread pool
		System.out.println( "This is server" );
        try
        {
            @SuppressWarnings("resource")
			ServerSocket ss = new ServerSocket( PORT );
            int count = 0;
            while ( true )
            {
                Socket s = ss.accept( );				   //Monitoring incoming connection request
                count++ ;								   //count supposedly uploaded to the .jsp page
                System.out.println( "\nThis is the " + count + "'st client connetion." );
    			shifterList.add(new FrequencyShifter(s));
                exec.execute(new SendFileSocket(s));       //Start another thread to execute service
            }
        } catch (Exception ex)
        {
            ex.printStackTrace( );
        }
    }

	public static void startMonitorInput(){
		ExecutorService execSingle = Executors.newSingleThreadExecutor();  
		execSingle.execute(commandReader);                   //start another single thread to monitor Frequency shift request via input reader
	}
	
	public static class CommandReader implements Runnable{
		private BufferedReader b = new BufferedReader(new InputStreamReader(System.in));
		int clientNum=-1;
		byte command='n';
		public void run(){
			while(true){
				try {
					String original=b.readLine();
					if(!original.matches("\\d+\\s[a-zA-Z]+")){
						System.out.println("Invalid command. Check the conventions carefully.");
					    continue;
					}
					String[] splitted=original.split(" ");  //split the command
					clientNum=Integer.parseInt(splitted[0]);
					command=(byte)splitted[1].charAt(0);    //only the first letter matters   
					if(clientNum<=shifterList.size()){
						shifterList.get(clientNum).shiftFrequency(command);
						System.out.println("Frequency altered.");
					}
					else System.out.println("No such a client.");
				} catch (IOException e) {
					System.out.println("Invalid command. Check the conventions carefully.");
//					e.printStackTrace();
				}
			}
		}
	}
}

class SendFileSocket implements Runnable{
	private Socket s;
	private String ip=new String("/null");
	SendFileSocket(Socket socket){
		this.s=socket;
	}

	
	public void run()
	{
		try
		{
            //s.setKeepAlive(true);                         //TCP heart beat mechanism
			ip=s.getInetAddress().toString();
			ReadData read=new ReadData(s);
			read.read();							        //read in package#1
			Decoder d=new Decoder(read.getAsString());		//pass the package#1 to CONSTRUCT the decoder
			d.decodeSpec();									//using package#1 to EQUIP the decoder
			d.putIp(ip);                                    //put IP into database
			while(!s.isInputShutdown()){                    //multiple data package transmission is allowed
				if(read.read()!=0)							//read in package#>=2, blocking; when read() returns 0, the transmission terminates
					d.decodeData(read.getAsString());       //decode data
				else break;                                 //reach the end of input stream
			}
			System.out.println("\nTransmission accomplished. Socket closing.");
			d.wipeIp(ip);
			s.close( );
		} catch (Exception e)
		{
			System.out.println("\nConnection aborted by client.");
			Database db=new Database();
			db.wipeIp(ip);                                  //wipe the IP when run into a exception
			try{s.close();}catch(Exception er){er.printStackTrace();}
//			e.printStackTrace( );
		}
	}
	
	public boolean isSocketAlive(){
		try{
			s.sendUrgentData(0xFF);
			return true;
		}catch(Exception e){
			return false;
		}
	}
}


class ReadData{
	private static final int BUFFER_SIZE=1024;
	private List<ByteBuffer> buffers=new ArrayList<ByteBuffer>();
	private Socket socket;
	public ReadData(Socket socket) throws IOException{
		this.socket=socket;
	}
	
	public int read() throws IOException{

		buffers.clear();
		InputStream in=socket.getInputStream();
		int k=0;
		byte r=0;
		while(true){
			ByteBuffer buffer=ByteBuffer.allocate(BUFFER_SIZE);
			for(k=0;k<BUFFER_SIZE;k++){
				r=(byte)in.read();
				if(r!=(byte)0xFF) buffer.put(r);			 //125 for now it ends with '}'// 255=0xFF
				else{
					buffer.put(r);							 //now we have a complete  package, ended with EOT
					buffer.flip();
					buffers.add(buffer);
					return k;                                //if k=0 here, then it is most possibly the end of input stream				
				}
			}
		}
   }

   public String getAsString() throws UnsupportedEncodingException{
	  StringBuffer str=new StringBuffer();
	  for(ByteBuffer buffer:buffers){
		str.append(new String(buffer.array(),0,buffer.limit(),"ISO-8859-1"));
	  }
	  return str.toString();
    }
}


class FrequencyShifter{
	private Socket s;
	FrequencyShifter(Socket socket) throws IOException{
		this.s=socket;
	}
	
	public boolean shiftFrequency(byte shiftByte){
		try{
			s.sendUrgentData(shiftByte);
			return true;
		}catch(Exception e){
			return false;
		}
	}
}

